#include <QtGui>
#include <QGLWidget>
#include <GL/glut.h>

#include "Vector/vector.03.h"
#include "Timer/timer.01.h"

const double pi = 3.141592653589793;

#define SQR(x) ((x)*(x))

 
class Visualizator: public QGLWidget {

	public:
		
		class Eye {
			public:	
		
				typedef float T;
		
				Eye(T Rx, T Ry, T Rz, T Cx, T Cy, T Cz, T Ux, T Uy, T Uz, T _fov):
				R(Rx,Ry,Rz), RM(R),
				U(Ux,Uy,Uz), UM(U),
				C(Cx,Cy,Cz), CM(C),
				PX(1,0,0), PY(0,1,0), PZ(0,0,1),		
				fov(_fov)
				{}
			
				fVector R,RM, U,UM,  C,CM; // M - memory
				fVector PX, PY, PZ;
		
				T fov;
		
				void rotateXY(T x, T y, bool apply){
	
					R=RM;	U=UM;
	
					T a1 = 0.5*pi-atan2(y,x); // angle btw U and XY
					T a2 = pi*sqrt(x*x+y*y);  // mouse shift on screen height (on 1) leads to rotion on pi 

					fVector RC = R-C;
					fVector XY = ROTATE(U, U%RC, a1);

					R = ROTATE(RC, XY, -a2)+C; // camera and object view motion are oposit 
					fVector XY_new = ROTATE(XY, RC, a2); // folow by the RV vector
	
					U = ORT(ROTATE(XY_new, ORT(XY%RC), -a1));
	
					if (apply) { RM = R; UM = U;}
	
				}	
		
				void rotateZ(T a, bool apply) {
	
					U = ROTATE(UM, UM%(R-C), a);

					if (apply) UM=U;
		
				}
		
				void moveZ(T z){

					RM=R = z*ORT(R-C)+C;

				}
		
			private:
		
				fVector ROTATE(fVector V1, fVector V2, T a){
					return module(V1)*((T)cos(a)*ORT(V1)+ (T)sin(a)*ORT(V2));	
				}

				fVector ORT(fVector V){ 
					return V.ort(); 
				}

				Eye::T module(fVector V){ 
					return V.module(); 
				}
		
		} eye;
		
		int scale;
		float scale_steps;
		float timeStep;
		float timeFlag;
		
		Timer wallTime;
		int glTimerID, fpsTimerID;
		double framesTime;
		int frames;
		
		Visualizator(QWidget *parent=0): QGLWidget(parent),
		eye(0,0,3, 0,0,0, 0,1,0, 45),
		mouseEvent0(0), 
		scale(0),
		scale_steps(10),
		timeStep(17), // [ms]
		framesTime(0),
		frames(0)
		{ 
			setAutoFillBackground(false);
			

			wallTime.set();
			wallTime.sin = 0.001*timeStep;
			glTimerID = startTimer(timeStep);
			fpsTimerID = startTimer(250); // fps info time
			timeFlag = true;
			
		};
		
		
		void timerEvent(QTimerEvent *event) {
			
			if (event->timerId() == glTimerID) {
				
				wallTime.set();
				if (timeFlag) moveTime(wallTime.sin+wallTime.sout);
				updateGL();
				
				framesTime+=wallTime.sin+wallTime.sout;
				frames++;
				wallTime.get();
				
			}
			
			if (event->timerId() == fpsTimerID){
				char str[100];
				sprintf(str, "% .1f", frames/framesTime);
				setWindowTitle( QString(str) );
				framesTime=frames=0;
			}
			
			
		}
		
		virtual void moveTime(double dt){
			/*
			for(int i=0; i<N; i++) {
				ROTATE(o[i].r, w*dt);
				o[i].v = w%o[i].r;
			}
			*/
		}
		
		void ROTATE(dVector &R, dVector w){
			
			if ((R%w).module() == 0) return;
			
			dVector wz = w.ort();
			dVector wy = (w%R).ort();
			dVector wx = (wz%wy).ort();
							
			dVector rz = (R*wz)*wz;
			dVector rx = (R*wx)*wx;
							
			rx = rx.rotate(wy, w.module());
				
			R = rz+rx;

		}
		
		
		void initializeGL() {  // Set up the rendering context, define display lists etc.:

			glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
			glShadeModel(GL_FLAT);
			glClearDepth(1.0);							// Enables Clearing Of The Depth Buffer
			glDepthFunc(GL_LESS);
			glEnable(GL_DEPTH_TEST);					// Enables Depth Testing
			glEnable(GL_CULL_FACE);

		}

		void resizeGL(int w, int h){  // setup viewport, projection etc.:

			glViewport(0, 0, w, h);
			glMatrixMode ( GL_PROJECTION );
			glLoadIdentity(); // загружает еденичную матрицу
			gluPerspective(eye.fov,(float)w/h, 0.001, 100);
			glMatrixMode (GL_MODELVIEW);

		}
		
		virtual void paintGL() {
			
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
			
			glMatrixMode(GL_MODELVIEW); 
			glLoadIdentity();
			gluLookAt(	eye.R.x, eye.R.y, eye.R.z, 
							eye.C.x, eye.C.y, eye.C.z, 
							eye.U.x, eye.U.y, eye.U.z );
			
			// PIVOT
			glPointSize(1);
			glBegin(GL_LINES);
			glColor3f (.5,0,0);
			glVertex3f(0,0,0);
			glVertex3f(1,0,0);
			
			glColor3f (0,.5,0);
			glVertex3f(0,0,0);
			glVertex3f(0,1,0);
   
			glColor3f (0,0,.5);
			glVertex3f(0,0,0);
			glVertex3f(0,0,1);
			glEnd();
			
			// draw axis lables
			glColor3f(0.7,0.4,0.4);
			glPrint(fVector(1.03,0,0), "x", GLUT_BITMAP_HELVETICA_12);
			glColor3f(0.4,0.7,0.4);
			glPrint(fVector(0,1.03,0), "y", GLUT_BITMAP_HELVETICA_12);
			glColor3f(0.4,0.4,.7);
			glPrint(fVector(0,0,1.03), "z", GLUT_BITMAP_HELVETICA_12);
			
			
			//glFlush();
		}
		
		QMouseEvent *mouseEvent0; 
		
		void onMouseMoveRelease(QMouseEvent event0, QMouseEvent event1, bool apply){
				
			const float pi = 3.141592654;
	
			// screen center coordinates on press
			float x0 =  (event0.x()-0.5*width())  / height();
			float y0 = -(event0.y()-0.5*height()) / height();
	
			// screen center coordinates on move / release
			float x1 =  (event1.x()-0.5*width())  / height();
			float y1 = -(event1.y()-0.5*height()) / height();
	
			float dx = x1-x0;
			float dy = y1-y0;
	
			float a0 = atan2(y0,x0);
			float a1 = atan2(y1,x1);
	
			float da = a1-a0; 
	
			//int modifers = QApplication::keyboardModifiers();
			int buttons = event0.buttons();
			
			// Rotation XY
			if (buttons == Qt::LeftButton)
				eye.rotateXY(dx, dy, apply);
	
			// Rotation around Z
			if (buttons == Qt::RightButton)
				eye.rotateZ(da, apply);
			
		}
		
		void wheelEvent(QWheelEvent *event){
			scale+=event->delta()/120.0f;
			eye.moveZ(3*pow(10, scale/scale_steps));
		}
		
		void mousePressEvent(QMouseEvent *event){
			mouseEvent0 = new QMouseEvent(*event);
		}

		void mouseMoveEvent(QMouseEvent *event) {
	      onMouseMoveRelease(*mouseEvent0, *event, false);
		}

		void mouseReleaseEvent(QMouseEvent *event){
			
			if (event->buttons()!=0) return; //??
			onMouseMoveRelease(*mouseEvent0, *event, true);

			if (mouseEvent0) delete mouseEvent0; 
			
		}
		
		double w_a1, w_a2;
		
		virtual void keyPressEvent(QKeyEvent *event){
			
			if (event->key() == Qt::Key_F) 
				setWindowState(windowState()^Qt::WindowFullScreen);
			
			if (event->key() == Qt::Key_Space)
				timeFlag = !timeFlag;
			
			if (event->key() == Qt::Key_X) {
				eye.RM = eye.R = eye.R.module()*fVector(1,0,0);
				eye.UM = eye.U = fVector(0,0,1);
			}
			
			if (event->key() == Qt::Key_Y) {
				eye.RM = eye.R = -eye.R.module()*fVector(0,1,0);
				eye.UM = eye.U = fVector(0,0,1);
			}
			
			if (event->key() == Qt::Key_Z) {
				eye.RM = eye.R = eye.R.module()*fVector(0,0,1);
				eye.UM = eye.U = fVector(0,1,0);
			}				
			
			if (event->key() == Qt::Key_Escape)
				if (windowState()) setWindowState(windowState()^Qt::WindowFullScreen);
				else exit(0);
			
		}
		
		
		dVector V;
		void glPrint(fVector R, const char *string, void *font) {
			glRasterPos3fv(R);
			int len = (int) strlen(string);
			for (int i = 0; i < len; i++) 
				glutBitmapCharacter(font, string[i]);
		}	
		
		
		
		void glPrint(int x, int y, const char *string, void *font) {
	
			// save current projection matrix
			glMatrixMode(GL_PROJECTION); 
			glPushMatrix();
	
			// get viewport size
			GLint m_viewport[4];
			glGetIntegerv(GL_VIEWPORT, m_viewport );
	
			// set projection matrix to ortho settings
			glLoadIdentity();
			glOrtho(0, m_viewport[2], m_viewport[3], 0, -1, 1);
	
			// save current modelview matrix
			glMatrixMode(GL_MODELVIEW); 
			glPushMatrix();
	
			//text drowing
			glLoadIdentity();

			int indent = 15; //
			glRasterPos2i(x, y);
			int len = (int) strlen(string);
			for (int i = 0; i < len; i++) {
		
				if (string[i] == '\n') {
					y+=indent;
					glRasterPos2i(x, y);
					continue;
				}
				glutBitmapCharacter(font, string[i]);
		
			}
	
			// restore previos settings
			glMatrixMode(GL_PROJECTION); 
			glPopMatrix();
	
			glMatrixMode(GL_MODELVIEW); 
			glPopMatrix();
			
		}
		
};

/* 
int main(int argc, char *argv[])
{
	glutInit(&argc, argv); //initialize glut
	QApplication app(argc, argv);
	MyQGLWidget window;
	
	window.resize(320, 240);
	window.show();
	window.setWindowTitle( QString("Visualizator Lite") );
	return app.exec();
}
*/